1.2 简介

本节简单预览一下语言功能,有个相对完整的印象更利于学习后续知识。

可依照第12章安装配置编译环境。

本书相关内容和示例运行环境:

  • Mac OS X EI Capitan 10.11.4
  • Ubuntu Server 14.04 x86_64
  • GNU gcc 5.3/gdb 7.7
  • Go 1.6 amd64

源文件

源码文件使用UTF-8编码,对Unicode支持良好。每个源文件都属于包的一部分,在文件头部用package声明所属包名称。

test.go

package main
  
func main() { 
   println("hello,world!") 
}

以“.go”作为文件扩展名,语句结束分号会被默认省略,支持C样式注释。入口函数main没有参数,且必须放在main包中。

用import导入标准库或第三方包。

package main

import( 
    "fmt" 
) 
  
func main() { 
   fmt.Println("hello,world!") 
}

请删除未使用的导入,否则编译器会将其当作错误。

可直接运行,或编译为可执行文件。

$go run main.go
  
hello,world!

变量

使用var定义变量,支持类型推断。基础数据类型划分清晰明确,有助于编写跨平台应用。编译器确保变量总是被初始化为零值,避免出现意外状况。

package main
  
func main() { 
   var x int32
   var s= "hello,world!" 
  
   println(x,s) 
}

在函数内部,还可省略var关键字,使用更简单的定义模式。

package main
  
func main() { 
   x:=100           // 注意,赋值符号不同 
   println(x) 
}
 

编译器将未使用的局部变量定义当作错误。

表达式

Go仅有三种流控制语句,与大多数语言相比,都可称得上简单。

if

func main() { 
   x:=100
  
   if x>0{ 
       println("x") 
    }else if x<0{ 
       println("-x") 
    }else{ 
       println("0") 
    } 
}

switch

func main() { 
   x:=100
  
   switch{ 
       case x>0: 
           println("x") 
       case x<0: 
           println("-x") 
       default: 
           println("0") 
    } 
}

for

func main() { 
   for i:=0;i<5;i++ { 
       println(i) 
    } 
  
   for i:=4;i>=0;i-- { 
       println(i) 
    } 
} 
 
 
func main() { 
   x:=0
  
   for x<5{      // 相当于while(x<5) { ... } 
       println(x) 
       x++ 
    } 
}
 
func main() { 
   x:=4
  
   for{           // 相当于while(true) { ... } 
       println(x) 
       x-- 
  
       if x<0{ 
           break
        } 
    } 
}

在迭代遍历时,for…range除元素外,还可返回索引。

func main() { 
   x:= []int{100,101,102} 
  
   for i,n:=range x{ 
       println(i, ":",n) 
    } 
}

输出:

0:100
1:101
2:102

函数

函数可定义多个返回值,甚至对其命名。

package main
  
import( 
    "errors" 
    "fmt" 
) 
func div(a,b int) (int,error) { 
   if b==0{ 
       return 0,errors.New("division by zero") 
    } 
  
   return a/b,nil
} 
  
func main() { 
   a,b:=10,2            // 定义多个变量 
   c,err:=div(a,b)           // 接收多返回值 
  
   fmt.Println(c,err) 
}
 

函数是第一类型,可作为参数或返回值。

func test(x int)func() {        // 返回函数类型 
   return func() {          // 匿名函数 
       println(x)            // 闭包 
    } 
} 
  
func main() { 
   x:=100
  
   f:=test(x) 
   f() 
}
 

用defer定义延迟调用,无论函数是否出错,它都确保结束前被调用。

package main
  
func test(a,b int) { 
   defer println("dispose...")    // 常用来释放资源、解除锁定,或执行一些清理操作 
                 // 可定义多个defer,按FILO顺序执行 
   println(a/b) 
} 
  
func main() { 
   test(10,0) 
}

输出:

$go run test.go
  
dispose... 
panic:runtime error:integer divide by zero

数据

切片(slice)可实现类似动态数组的功能。

package main
  
import( 
    "fmt" 
) 
  
func main() { 
   x:=make([]int,0,5)        // 创建容量为5的切片 
  
   for i:=0;i<8;i++ { 
       x=append(x,i)       // 追加数据。当超出容量限制时,自动分配更大的存储空间 
    } 
  
   fmt.Println(x) 
}
 

输出:

[0 1 2 3 4 5 6 7]

将字典(map)类型内置,可直接从运行时层面获得性能优化。

package main
  
import( 
    "fmt" 
) 
  
func main() { 
   m:=make(map[string]int)      // 创建字典类型对象 
  
   m["a"] =1             // 添加或设置 
  
   x,ok:=m["b"]          // 使用ok-idiom获取值,可知道key/value是否存在 
   fmt.Println(x,ok) 
  
   delete(m, "a")           // 删除 
}
 

所谓ok-idiom模式,是指在多返回值中用一个名为ok的布尔值来标示操作是否成功。因为很多操作默认返回零值,所以须额外说明。

结构体(struct)可匿名嵌入其他类型。

package main
  
import( 
    "fmt" 
) 
  
type user struct{          // 结构体类型 
   name string
   age byte
} 
  
type manager struct{ 
   user               // 匿名嵌入其他类型 
   title string
} 
  
func main() { 
   var m manager
  
   m.name= "Tom"           // 直接访问匿名字段的成员 
   m.age=29
   m.title= "CTO" 
  
   fmt.Println(m) 
}

输出:

{{Tom 29}CTO}

方法

可以为当前包内的任意类型定义方法。

package main
  
type X int
  
func(x*X)inc() {         // 名称前的参数称作receiver,作用类似python self
    *x++ 
} 
  
func main() { 
   var x X
   x.inc() 
   println(x) 
}
 

还可直接调用匿名字段的方法,这种方式可实现与继承类似的功能

package main
  
import( 
    "fmt" 
) 
  
type user struct{ 
   name string
   age byte
} 
  
func(u user)ToString()string{ 
   return fmt.Sprintf("%+v",u) 
} 
  
type manager struct{ 
   user
   title string
} 
  
func main() { 
   var m manager
   m.name= "Tom" 
   m.age=29
  
   println(m.ToString())     // 调用user.ToString() 
}
 

接口

接口采用了duck type方式,也就是说无须在实现类型上添加显式声明。

package main
  
import( 
    "fmt" 
) 
  
type user struct{ 
   name string
   age byte
} 
  
func(u user)Print() { 
   fmt.Printf("%+v\n",u) 
} 
  
type Printer interface{     // 接口类型 
   Print() 
} 
  
func main() { 
   var u user
   u.name= "Tom" 
   u.age=29
  
   var p Printer=u        // 只要包含接口所需的全部方法,即表示实现了该接口 
   p.Print() 
}

另有空接口类型interface{},用途类似OOP里的system.Object,可接收任意类型对象

并发

整个运行时完全并发化设计。凡你能看到的,几乎都在以goroutine方式运行。这是一种比普通协程或线程更加高效的并发设计,能轻松创建和运行成千上万的并发任务。

package main
  
import( 
    "fmt" 
    "time" 
) 
  
func task(id int) { 
   for i:=0;i<5;i++ { 
       fmt.Printf("%d: %d\n",id,i) 
       time.Sleep(time.Second) 
    } 
} 
  
func main() { 
   go task(1)                  // 创建goroutine
   go task(2) 
  
   time.Sleep(time.Second*6) 
}
 

输出:

1:0
2:0
1:1
2:1
1:2
2:2
2:3
1:3
2:4
1:4  

通道(channel)与goroutine搭配,实现用通信代替内存共享的CSP模型。

package main
  
// 消费者 
func consumer(data chan int,done chan bool) { 
   for x:=range data{         // 接收数据,直到通道被关闭 
       println("recv:",x) 
    } 
  
   done<-true            // 通知main,消费结束 
} 
  
// 生产者 
func producer(data chan int) { 
   for i:=0;i<4;i++ { 
       data<-i            // 发送数据 
    } 
  
   close(data)              // 生产结束,关闭通道 
} 
  
func main() { 
   done:=make(chan bool)           // 用于接收消费结束信号 
   data:=make(chan int)            // 数据管道 
  
   go consumer(data,done)           // 启动消费者 
   go producer(data)            // 启动生产者 
  
    <-done                 // 阻塞,直到消费者发回结束信号 
}

输出:

recv:0
recv:1
recv:2
recv:3